#include "../lib/corechain.hpp"
#include "tinyxml.h"
#include "../lib/coreqmanager.hpp"

using namespace pland;

Chain::Chain()
{
    utill_log(std::string(__FILE__) + " New chain created");
    ready_ = false;
    running_ = false;
}

Chain::~Chain()
{
    using std::map;
    utill_log(std::string(__FILE__) + " Deleting chain");
    if(running_ == true)
        stop();
    QueueManager *qman = QueueManager::Instance();
    map<int,Chain_Plugin>::iterator iter;
    for(iter = plugins_.begin(); iter != plugins_.end(); iter++)
    {
        qman->removeQueueu(iter->second.net);
        delete iter->second.object;
    }
}

int Chain::create_chain(std::string config_xml) throw(Exeption)
{
    using std::string;

    boost::mutex::scoped_lock lock(mutex_);
    if(ready_)
        return -1;
    QueueManager *qman = QueueManager::Instance();

    if(!util_fexists(config_xml))
    {
        utill_log(string(__FILE__) + string(" No config file ") + config_xml);
        throw Exeption("No config file " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
    }
    TiXmlDocument doc(config_xml);
    if(!doc.LoadFile())
    {
        utill_log(string(__FILE__) + string(" Bad config file ") + config_xml);
        throw Exeption("Bad config file " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
    }
    TiXmlNode *root_node = doc.RootElement();
    //Load Pluggins
    TiXmlNode *plugin_node = root_node->FirstChild("plugin");
    while(plugin_node != NULL)
    {
        if(plugin_node->FirstChild("net") == NULL || plugin_node->FirstChild("path") == NULL || plugin_node->FirstChild("id") == NULL)
        {
            utill_log(string(__FILE__) + " chain graph: " +config_xml +  " mallformed plugin");
            throw Exeption("Mallformed config " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
        }

        int id = utill_to_int(plugin_node->FirstChild("id")->FirstChild()->Value());

        Chain_Plugin temp;
        temp.net = plugin_node->FirstChild("net")->FirstChild()->Value();
        temp.path = plugin_node->FirstChild("path")->FirstChild()->Value();

        temp.object = PLoader::loadPlugin(temp.path,id);

        if(temp.object == NULL)
        {
            utill_log(string(__FILE__) + " Could not load object: " + temp.path);
            throw Exeption("Mallformed config " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
        }
        utill_log(string(__FILE__) + " Loaded object " + temp.path + " with net name " + temp.net + " and id " + utill_to_string(temp.object->getId()));
        plugins_.insert(std::make_pair(id,temp));
        plugin_node = root_node->IterateChildren("plugin", plugin_node);
    }


    TiXmlNode *queue_node = root_node->FirstChild("queue");
    while(queue_node != NULL)
    {
        if((queue_node->FirstChild("net") == NULL) || ((queue_node->FirstChild("sink") == NULL ) && (queue_node->FirstChild("source") == NULL )) )
        {
            utill_log(string(__FILE__) + " Malformed XML Queue Definition");
            throw Exeption("Mallformed config " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
        }
        Chain_Queue temp;
        temp.net = queue_node->FirstChild("net")->FirstChild()->Value();

        if(queue_node->FirstChild("sink") != NULL)
        {
            TiXmlNode *sink = queue_node->FirstChild("sink");

            while(sink != NULL)
            {
                temp.sinks.push_back(utill_to_int(sink->FirstChild()->Value()));
                sink = queue_node->IterateChildren("sink",sink);
            }
        }

        if(queue_node->FirstChild("source") != NULL)
        {
            TiXmlNode *source = queue_node->FirstChild("source");
            while(source != NULL)
            {
                temp.sources.push_back(utill_to_int(source->FirstChild()->Value()));
                source = queue_node->IterateChildren("source",source);
            }
        }

        string log = string(__FILE__) + " Created queue: " + temp.net + ", of type: ";
        temp.queue = SharedDQueue(new DQueue());
        qman->addQueueu(temp.net, temp.queue);

        if(temp.sources.empty())
        {
            temp.type = SOURCE;
            log += "INPUT";
        }
        else if(temp.sinks.empty())
        {
            temp.type = SINK;
            log += "OUTPUT";
        }
        else
        {
            temp.type = INTERNAL;
            log += "INTERNAL";
        }
        utill_log(log);
        queues_.push_back(temp);
        queue_node = root_node->IterateChildren("queue",queue_node);
    }

    TiXmlNode *split_node = root_node->FirstChild("split");
    while(split_node != NULL)
    {
        if((split_node->FirstChild("net") == NULL) || ((split_node->FirstChild("id") == NULL ) && (split_node->FirstChild("type") == NULL )) )
        {
            utill_log(string(__FILE__) + " Malformed XML Queue Definition");
            throw Exeption("Mallformed config " + config_xml, PEXP_BAD_CONFIG | PEXP_FATAL);
        }

        int id = utill_to_int(split_node->FirstChild("id")->FirstChild()->Value());
        string type =  split_node->FirstChild("type")->FirstChild()->Value();
        Chain_Spitter temp;
        temp.net = split_node->FirstChild("net")->FirstChild()->Value();
        string log = string(__FILE__) + " Created Splitter: " + temp.net + ", of type: ";
        if(type == "balance")
        {
            temp.type = BALANCE;
            temp.splitter = new Splitter_Balancer();
            log += " BALANCE";
        }
        if(type == "copy")
        {
            temp.type = COPY;
            temp.splitter = new Splitter_Copy();
            log += " COPY";
        }
        splitters_.insert(std::make_pair(id,temp));
        utill_log(log);
        split_node = root_node->IterateChildren("split", split_node);
    }
    loadQueues_();
    ready_ = true;
    return 0;
}

void Chain::loadQueues_()
{
    using std::map;
    using std::string;
    for(size_t q= 0; q< queues_.size(); q++)
    {
        if(queues_.at(q).type == INTERNAL || queues_.at(q).type == SINK )
        {
            for(size_t s = 0; s < queues_.at(q).sources.size(); s++)
            {
                int source = queues_.at(q).sources.at(s);
                if(plugins_.find(source) != plugins_.end())
                {
                    plugins_.at(source).qOut = queues_.at(q).queue;
                    utill_log(string(__FILE__) + " Queue " + queues_.at(q).net + " sink for " + plugins_.at(source).net);
                }
                else
                {
                    splitters_.at(source).qOut.push_back(queues_.at(q).queue);
                    utill_log(string(__FILE__) + " Queue " + queues_.at(q).net + " sink for splitter " + splitters_.at(source).net);
                }
            }
        }
        if(queues_.at(q).type == INTERNAL || queues_.at(q).type == SOURCE )
        {
            for(size_t s = 0; s<queues_.at(q).sinks.size(); s++)
            {
                int sink = queues_.at(q).sinks.at(s);
                if(plugins_.find(sink) != plugins_.end())
                {
                    plugins_.at(sink).qIn = queues_.at(q).queue;
                    utill_log(string(__FILE__) + " Queue " + queues_.at(q).net + " source for task " + plugins_.at(sink).net);
                }
                else
                {
                    splitters_.at(sink).qIn = queues_.at(q).queue;
                    utill_log(string(__FILE__) + " Queue " + queues_.at(q).net + " source for splitter " + splitters_.at(sink).net);
                }
            }
        }
    }

    for(map<int,Chain_Plugin>::iterator iter = plugins_.begin(); iter != plugins_.end(); iter++)
    {
        Chain_Plugin p = iter->second;
        p.object->getUserPlugin()->load_queues(p.qIn,p.qOut);
    }
    for(map<int,Chain_Spitter>::iterator iter = splitters_.begin(); iter != splitters_.end(); iter++)
    {
        Chain_Spitter p = iter->second;
        p.splitter->loadQueues(p.qIn,p.qOut);
    }
}

std::vector<Chain::Chain_Queue> Chain::sourceQueues()
{
    std::vector<Chain::Chain_Queue> ret;
    for(uint q = 0; q < queues_.size(); q++)
    {
        if(queues_.at(q).type == SOURCE)
            ret.push_back(queues_.at(q));
    }
    return ret;
}

std::vector<Chain::Chain_Queue> Chain::sinkQueues()
{
    std::vector<Chain::Chain_Queue> ret;
    for(uint q = 0; q < queues_.size(); q++)
    {
        if(queues_.at(q).type == SINK)
            ret.push_back(queues_.at(q));
    }
    return ret;
}

std::vector<Chain::Chain_Queue> Chain::queues()
{
    return queues_;
}

void Chain::start()
{
    boost::mutex::scoped_lock lock(mutex_);
    if(running_ == true || ready_ == false)
    {
        return;
    }
    for(std::map<int,Chain_Plugin>::iterator iter = plugins_.begin(); iter != plugins_.end(); iter++)
    {
        Chain_Plugin p = iter->second;
        p.object->start();
    }
    for(std::map<int,Chain_Spitter>::iterator iter = splitters_.begin(); iter != splitters_.end(); iter++)
    {
        Chain_Spitter p = iter->second;
        p.splitter->start();
    }
    running_ = true;
}

void Chain::stop()
{
    boost::mutex::scoped_lock lock(mutex_);
    if(running_ == false || ready_ == false)
    {
        return;
    }
    for(std::map<int,Chain_Plugin>::iterator iter = plugins_.begin(); iter != plugins_.end(); iter++)
    {
        iter->second.object->stop();
    }
    for(std::map<int,Chain_Spitter>::iterator iter = splitters_.begin(); iter != splitters_.end(); iter++)
    {
        iter->second.splitter->stop();
    }
    running_ = false;
}

bool Chain::status()
{
    return running_;
}
